package tools

import (
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/retry"
	"github.com/hyperledger/fabric-sdk-go/pkg/common/errors/status"
	contextAPI "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/context"
	fabAPI "github.com/hyperledger/fabric-sdk-go/pkg/common/providers/fab"
	contextImpl "github.com/hyperledger/fabric-sdk-go/pkg/context"
	"math"
	"net/http"
	"time"
)

var (
	secret   = []byte("blockChain")
	noVerify = []string{"/admin/acl/index/login", "txEvent", "blockEvent"} //该路由下不校验token
)

// JwtClaims 创建Claims
type JwtClaims struct {
	*jwt.StandardClaims
	//用户账号
	Userid   string
	Username string
}

// CreateJwtToken 生成一个jwt token
func CreateJwtToken(userid string, username string) (signedToken string, err error) {
	claims := JwtClaims{
		StandardClaims: &jwt.StandardClaims{
			NotBefore: int64(time.Now().Unix()),                         // token生效时间
			ExpiresAt: int64(time.Now().Add(time.Hour * 24 * 7).Unix()), // token过期时间
			Issuer:    "SEU",                                            // 发布者
		},
		Userid:   userid,
		Username: username,
	}
	// 对自定义claims加密,使用jwt.SigningMethodHS256加密算法得到token第二部分
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	// 签名得到第三部分,得到一个完整的三段的加密
	signedToken, err = token.SignedString(secret)
	if err != nil {
		return "", err
	}
	return signedToken, nil
}

//ParseJwtToken 解析token
func ParseJwtToken(jwtToken string) (*JwtClaims, error) {
	token, err := jwt.ParseWithClaims(jwtToken, &JwtClaims{}, func(*jwt.Token) (interface{}, error) {
		return secret, nil
	})
	if claims, ok := token.Claims.(*JwtClaims); ok && token.Valid {
		return claims, err
	} else {
		return nil, err
	}
}

//CalculateStartAndEnd 根据总数、页码、页面限制计算总页数、开始和结束编号
func CalculateStartAndEnd(sum int, page int, limit int) (pages int, start int, end int) {
	pages = int(math.Ceil(float64(sum) / float64(limit)))

	// 如果为最后一页
	if pages == page {
		end = sum
		start = (pages-1)*limit + 1
	} else {
		end = page * limit
		start = end - limit + 1
	}
	return
}

// CheckJWT 中间件检查token，若检查成功则注入到上下文Context中
func CheckJWT() gin.HandlerFunc {
	return func(c *gin.Context) {
		//判断是否需要中间件处理
		if IsContainArr(noVerify, c.Request.RequestURI) {
			return
		}
		//从header中获取token
		tokenString := c.GetHeader("token")
		if tokenString == "" {
			c.JSON(419, gin.H{"code": 419, "msg": "unauthorized"})
			c.Abort()
			return
		}
		//解析token
		claims, err := ParseJwtToken(tokenString)
		if err != nil {
			c.JSON(419, gin.H{"code": 419, "msg": err.Error()})
			c.Abort()
			return
		}
		c.Set("userid", claims.Userid)
		c.Set("username", claims.Username)
		fmt.Println("CheckJWT----", claims.Username)
		c.Next()
	}
}

//
func IsContainArr(arr []string, s string) bool {
	for _, v := range arr {
		if v == s {
			return true
		}
	}
	return false
}

// 跨域认证
func Cors() gin.HandlerFunc {
	return func(context *gin.Context) {
		method := context.Request.Method
		context.Header("Access-Control-Allow-Origin", "*")
		context.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token")
		context.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
		context.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type, token")
		context.Header("Access-Control-Allow-Credentials", "true")
		if method == "OPTIONS" {
			context.AbortWithStatus(http.StatusNoContent)
		}
		context.Next()
	}
}

// ElementInSlice 判断e是在sli中
func ElementInSlice(e string, sli []string) bool {
	for _, s := range sli {
		if e == s {
			return true
		}
	}
	return false
}

func DiscoverLocalPeers(ctxProvider contextAPI.ClientProvider, expectedPeers int) ([]fabAPI.Peer, error) {
	ctx, err := contextImpl.NewLocal(ctxProvider)
	if err != nil {
		return nil, fmt.Errorf("error creating local context: %v", err)
	}

	discoveredPeers, err := retry.NewInvoker(retry.New(retry.TestRetryOpts)).Invoke(
		func() (interface{}, error) {
			peers, serviceErr := ctx.LocalDiscoveryService().GetPeers()
			if serviceErr != nil {
				return nil, fmt.Errorf("getting peers for MSP [%s] error: %v", ctx.Identifier().MSPID, serviceErr)
			}
			if len(peers) < expectedPeers {
				return nil, status.New(status.TestStatus, status.GenericTransient.ToInt32(), fmt.Sprintf("Expecting %d peers but got %d", expectedPeers, len(peers)), nil)
			}
			return peers, nil
		},
	)
	if err != nil {
		return nil, err
	}

	return discoveredPeers.([]fabAPI.Peer), nil
}
